defmodule Pango.CypherFilter do
	@moduledoc false
	require Logger

	def where(target_mod, condition, target_obj, with \\ []) when is_atom(target_mod) do
		where(target_mod.fields(), condition, target_obj, with)
	end
	def where(fields, condition, target_obj, with)  do
		Logger.debug("target_obj #{target_obj}, filter: #{inspect(condition)}")
		{str, {params, with}} = EnumKit.reduce_map_join(
			condition,
			" AND ",
			{%{}, with},
			fn
				{func, where}, params_acc when is_function(func) ->
					func.(where, target_obj, params_acc)
				{operator, where}, params_acc when is_map(where) ->
					if map_size(where) > 0 do
						match(operator, " AND ", target_obj, where, fields, params_acc)
					else
						:skip
					end
				{key, value}, params_acc when is_list(value) ->
					parse_key_filter(nil, target_obj, fields, key, value, params_acc)
				{key, value}, params_acc ->
					parse_key_filter(nil, target_obj, fields, key, value, params_acc)
				_, _ ->
					PrivateCandy.remind(__ENV__)
			end
		)
		if str != "" do
			{" where " <> str, params, with}
		else
			{"", params, with}
		end
	end

	def match("OR", _, target_obj, where, fields, params_acc) do
		{str, params_acc} = EnumKit.reduce_map_join(
			where,
			" OR ",
			params_acc,
			fn
				{"like" = operator, value}, params_acc ->
					match(operator, " AND ", target_obj, value, fields, params_acc)
				{"sql", {str, key, v}}, {params_acc, with_acc} ->
					{str, {Map.put(params_acc, key, v), with_acc}}
				{"sql", str}, params_acc ->
					{str, params_acc}
				{key, value}, params_acc ->
					parse_key_filter(nil, target_obj, fields, key, value, params_acc)
			end
		)
		{"(" <> str <> ")", params_acc}
	end
	def match(op, _, target_obj, where, _fields, acc) when op in ["between", "BETWEEN"] do
		EnumKit.reduce_map_join(
			where,
			" AND ",
			acc,
			fn
				{key, [v1, v2]}, {params_acc, with_acc} ->
					{obj, key, min_v, max_v} = case String.split(key, "->") do
						[obj, field] ->
							key = obj <> "." <> field
							v = obj <> "_" <> field
							{obj, key, v <> "_min", v <> "_max"}
						_ ->
							key1 = target_obj <> "." <> key
							{target_obj, key1, key <> "_min", key <> "_max"}
					end
					str = "#{key} >= $#{min_v} and #{key} <= $#{max_v}"
					params_acc = Map.put(params_acc, min_v, v1)
											 |> Map.put(max_v, v2)
					{str, {params_acc, [obj | with_acc]}}
			end
		)
	end
	def match("to_date", f, target_obj, where, _fields, acc) do
		EnumKit.reduce_map_join(
			where,
			f,
			acc,
			fn
				{key, value}, {params_acc, with_acc} ->
					{obj, key, v} = case String.split(key, "->") do
						[obj, field] ->
							key = obj <> "." <> field
							v = obj <> "_" <> field
							{obj, key, v}
						_ ->
							key1 = target_obj <> "." <> key
							{target_obj, key1, key}
					end
					{"left(#{key}, 10) = $#{v}", {Map.put(params_acc, v, value), [obj | with_acc]}}
			end
		)
	end
	def match(operator, joiner, target_obj, where, fields, {params_acc, with_acc} = acc) when is_map(where) do
		String.contains?(operator, ["//", "or", "true", " "]) &&
			PrivateCandy.debug_remind(__ENV__, "#{operator}, input_error")
		case Map.get(fields, operator) do
			%{type: type} ->
				parse_key_filter(operator, target_obj, fields, operator, where, acc)
			#				concat2(type, nil, operator, target_obj <> "." <> operator, where, params_acc, with_acc)
			_ ->
				EnumKit.reduce_map_join(
					where,
					" AND ",
					acc,
					fn
						{key, value}, params_acc ->
							parse_key_filter(operator, target_obj, fields, key, value, params_acc)
					end
				)
		end
	end
	def match(_operator, _joiner, _target_obj, _where, _fields, params_acc) do
		PrivateCandy.remind(__ENV__)
	end

	def parse_key_filter(operator, target_obj, fields, key, v, {params_acc, with_acc}) do
		{obj, field1, key, param_key, with_acc} = case String.split(key, "->") do
			[obj, field1] ->
				{obj, field1, obj <> "." <> field1, obj <> "_" <> field1, [obj | with_acc]}
			_ ->
				{nil, key, target_obj <> "." <> key, key, with_acc}
		end
		field = case Map.get(fields, obj) do
			# 先判断是否是关系对象
			%{relation_object: _} = rel_fileds ->
				Map.get(rel_fileds, field1, %{})
			%{mod: _mod} = a ->
				a
			_ ->
				f = Map.get(fields, field1)
				f != nil || PrivateCandy.debug_remind(__ENV__, "该参数错误,key: #{key}, v:#{v}")
				f
		end
		case field do
			%{mod: mod} ->
				v = DeeprConvert.lookup_convert(mod.fields(), field1, v)
				type = MapLib.get_in(mod.fields(), [field1, :type])
				case concat(operator, key, param_key, type, v) do
					nil ->
						:skip
					str ->
						{str, {Map.put(params_acc, param_key, v), with_acc}}
				end
			_ ->
				v = DeeprConvert.lookup_convert(fields, field1, v)
				concat2(Map.get(field, :type), operator, param_key, key, v, params_acc, with_acc)
		end
	end

	def concat2(:jsonb, _operator, param_key, key, v, params_acc, with_acc) do
		if is_list(v) do
			{str, {_, params_acc}} = EnumKit.reduce_map_join(
				v,
				" and ",
				{1, params_acc},
				fn (h, acc = {index, params_acc1}) ->
					is_map(h) || PrivateCandy.debug_remind(__ENV__, "input_error")
					if map_size(h) > 0 do
						pk = param_key <> "#{index}"
						str = key <> " contains($#{pk})"
						{str, {index + 1, Map.put(params_acc1, pk, String.replace(Jason.encode!(h), ["{", "}"], ""))}}
					else
						acc
					end
				end
			)
			{str, {params_acc, with_acc}}
		else
			is_map(v) || PrivateCandy.debug_remind(__ENV__, "input_error")
			if map_size(v) > 0 do
				str = key <> " contains($#{param_key})"
				{str, {Map.put(params_acc, param_key, String.replace(Jason.encode!(v), ["{", "}"], "")), with_acc}}
			else
				:skip
			end
		end
	end
	def concat2(type, operator, param_key, key, v, params_acc, with_acc) do
		case concat(operator, key, param_key, type, v) do
			nil ->
				:skip
			str ->
				{str, {Map.put(params_acc, param_key, v), with_acc}}
		end
	end

	#	def concat(_, key, param_key, :jsonb, v) do
	#		if is_list(v) do
	#			EnumKit.reduce_map_join(
	#				v,
	#				" or ",
	#				1,
	#				fn (h, index) ->
	#					is_map(h) || PrivateCandy.debug_remind(__ENV__, "input_error")
	#					{key <> " contains($#{param_key}#{index})", index + 1}
	#				end
	#			)
	#			|> IO.inspect()
	#		else
	#			key <> " contains($#{param_key})"
	#		end
	#	end

	def concat("like", key, value, _type, v) do
		is_empty = is_empty(v)
		if !is_empty do
			if is_list(v)   do
				"any(x in $#{value} where " <> key <> " CONTAINS(x)"
			else
				key <> " contains($#{value})"
			end
		else
			nil
		end
	end
	#	def concat("AnyIn", key, value, _type, _) do
	#		if is_list(value) and CheckUtil.not_empty_str?(value) != [] do
	#			"ANY(x IN $#{value} WHERE x IN $#{value})"
	#		else
	#			nil
	#		end
	#	end

	def concat(operator, key, param_key, type, v) when operator in [nil, "not"] do
		if !is_empty(v) do
			is_array = is_tuple(type) and elem(type, 0) == :array or type == :one_to_many
			str = if operator == "not" do
				"not "
			else
				""
			end
			case type do
				_ when is_list(v) and is_array ->
					str <> "ANY(x IN #{key} WHERE x IN $#{param_key})"
				_t when is_list(v) ->
					str <> key <> " IN $" <> param_key
				_ when is_array ->
					str <> "$#{param_key} IN " <> key
				_ ->
					str <> key <> " = $#{param_key}"
			end
		else
			nil
		end
	end
	def concat(operator, key, param_key, _type, v) do
		if is_empty(v) do
			nil
		else
			key <> " " <> operator <> " $" <> param_key
		end
	end
	def is_empty(v) when is_binary(v) do
		String.trim(v) == ""
	end
	def is_empty(v) when is_map(v) do
		map_size(v) == 0
	end
	def is_empty(v) when is_list(v) do
		v == [] || !CheckUtil.not_empty_str?(v)
	end
	def is_empty(v) do
		v == nil
	end

end
